// webpack 做的事:  静态资源打包器  将js jquery less css vue 等 bunch (代码块)打包成bundle.js  ==> 能在浏览器运行 被浏览器识别
// entry 入口
// outPut输出口
// loader 翻译 非js能识别的东西

/*loader的标准使用方式:
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {}
                    },
                    ...
                ]
            }
        ]
    }*/

// plugin 扩展功能
// mode: 开发模式 development  与生产模式 production(做的事情更多)

/*
  webpack.config.js  webpack的配置文件
    作用: 指示 webpack 干哪些活（当你运行 webpack 指令时，会加载里面的配置）
    所有构建工具都是基于nodejs平台运行的 ~ 模块化默认 采用commonjs 。 而前端项目采用的是ES6 不要混淆

   loader: 1. 下载   2. 使用（配置loader）
   plugins: 1. 下载  2. 引入  3. 使用
*/

// resolve用来拼接绝对路径的方法
const {resolve} = require('path');
console.log("__dirname:", __dirname)

// 设置nodejs环境变量 开发环境才会生效
process.env.NODE_ENV = 'development';

const HtmlWebpackPlugin = require('html-webpack-plugin');  // 处理html的plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin');//★单独提取css
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')//压缩css★
// const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //压缩js
// const TerserWebpackPlugin = require('terser-webpack-plugin') // 了解 在optimization配置

/*PWA: 渐进式网络开发应用程序(离线可访问)  兼容性问题 使用的不多  使用 workbox --> 下载workbox-webpack-plugin         了解学习*/
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');//安装  webpack插件

//dll的使用
const webpack = require('webpack');
//
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');


/*// 代码分割js的方式1: 多入口 一个js 指定输出一个js
entry: {
    // 多入口：几个入口，最终输出就对应几个bundle.js
    index: './src/js/index.js',
    test2: './src/js/test2.js'
},
    // 输出
    output: {
    // [name]：取文件名
    filename: 'js/[name].[contenthash:10].js',
        path: resolve(__dirname, 'build')
},*/

/*
  entry: 入口起点 详细配置
    1. string --> './src/index.js'
      单入口
      打包形成一个chunk。 输出一个bundle文件。
      此时chunk的名称默认是 main
    2. array  --> ['./src/index.js', './src/add.js']
      多入口
      所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
        --> 只有在HMR功能中让html热更新生效~才会用这种
    3. object
      多入口 1进js对应1个输出js
      有几个入口文件就形成几个chunk，输出几个bundle文件
      此时chunk的名称是 key

      --> 23 混用
        {
          // 所有入口文件最终只会形成一个chunk, 输出出去只有一个bundle文件。
          index: ['./src/index.js', './src/count.js'],
          // 形成一个chunk，输出一个bundle文件。
          add: './src/add.js'
        }
*/
// webpack配置
module.exports = {
    // 入口起点
    // entry: ['./src/index.js','./src/index.html'], html的hmr 不用做
    entry: './src/index.js',
    target:"web",
    // 输出
    output: {
        // filename: 'js/built[hash:10].js',  // 输出文件名 C:\Users\16674\Documents\webpackStudying/build/js/built.js(使用webpack 打包才有作用)
        filename: 'js/built[contenthash:10].js',
        path: resolve(__dirname, 'build'),// 输出路径 __dirname是nodejs的变量，代表当前文件的目录绝对路径 ==> C:\Users\16674\Documents\webpackStudying

        // 所有资源引入公共路径前缀  比如打包后的html里面引入 --> eg: 'imgs/a.jpg' ==> '/imgs/a.jpg'
        // publicPath: '/',
        // chunkFilename: 'js/[name]_chunkkk.js', // 修改非入口chunk的名称   不设置用的规则就是filename的规则
        // library: '[name]', // 整个库向外暴露的变量名
        // libraryTarget: 'window' // 变量名添加到哪个上 browser
        // libraryTarget: 'global' // 变量名添加到哪个上 node
        // libraryTarget: 'commonjs'
    },
    // loader的配置 作用: 识别js 不能识别的资源
    // oneOf 匹配一个loader 之后就不会继续往下找 提高性能
    module: {
        rules: [
                {
                  test: /\.js$/,
                  //要排除
                  exclude: /node_modules/,
                  loader: 'eslint-loader',
                  enforce:'pre',
                  options: {
                      // 自动修复 (你写的代码hui被eslint识别并改正)  会有警告 不会爆红
                      fix: true
                  }
              },
            {
                // 以下loader只会匹配一个 主要为了提升性能
                // 注意：不能有两个配置处理同一种类型(css js)文件 要提出来一个
                // oneOf 找到即用 不会无脑找下去
                oneOf:[
                    // 详细loader配置
                    // 不同文件必须配置不同loader处理
                    {
                        // test:  匹配哪些文件
                        test: /\.css$/,
                        // use: 使用哪些loader进行处理 ;use数组中loader执行顺序：从右到左，从下到上 依次执行
                        // 普通配置
                        /*   use: [
                               // 创建style标签，将js中的样式资源插入进去，添加到head中生效  // 要下载
                               'style-loader',
                               // 将css文件变成commonjs模块加载js中，里面内容是样式字符串  // 要下载
                               'css-loader'
                           ],*/

                        // 提取css成为单独的css文件的 配置
                        use: [
                            // 'style-loader' 创建style标签，将样式放入
                            // 这个loader只取代style-loader。作用：提取js中的css成单独文件★
                            MiniCssExtractPlugin.loader,
                            // 将css文件整合到js文件中
                            'css-loader',
                            // 'style-loader',
                            /*  这些都在package.json中配置即可
                             css兼容性处理：postcss --> 下载postcss-loader 和postcss-preset-env
                             帮postcss找到package.json中browserslist里面的配置，通过配置加载指定的css兼容性样式
                             //去guihub找
                             "browserslist": {
                               // 开发环境配置(要想开发环境需要)设置node环境变量：process.env.NODE_ENV = development 才有效果
                               //兼容最近的一个浏览器版本
                               "development": [
                                 "last 1 chrome version",
                                 "last 1 firefox version",
                                 "last 1 safari version"
                               ],
                               // 生产环境：默认是看生产环境
                               "production": [
                                 //或者更小
                                 ">0.2%",
                                 //不要ie
                                 "not dead",
                                 //不要 op_mini
                                 "not op_mini all"
                               ]
                             }
                           */
                            // css兼容性处理  postcss-loader的配置
                            // 正确的写法
                            {
                                loader: 'postcss-loader',
                                options: {
                                    // 包裹一层postcssOptions
                                    postcssOptions: {
                                        plugins: [
                                            ['postcss-preset-env',{}]
                                        ]
                                    }
                                }
                            },
                        ]
                    },
                    // 将less 也合并到css里面去(暂时别做会有问题)
                    {
                        test: /\.less$/,
                        use: [
                            {loader:'style-loader'},
                            {loader:'css-loader'},
                            {loader:'less-loader'}
                        ]
                        /*        use: [
                                    // 'style-loader',
                                    MiniCssExtractPlugin.loader,
                                    'css-loader',
                                    {
                                        loader: 'postcss-loader',
                                        options: {
                                            // 包裹一层postcssOptions
                                            postcssOptions: {
                                                plugins: [
                                                    ['postcss-preset-env',{}]
                                                ]
                                            }
                                        }
                                    },
                                    // 将less文件编译成css文件   需要下载 less-loader和less
                                    'less-loader'
                                ]*/

                    },

                    {
                        // 问题：默认处理不了html中img图片
                        // 处理图片资源
                        test: /\.(jpg|png|gif|jepg)$/,
                        // 使用一个loader
                        // 下载 url-loader 依赖于file-loader  所以俩都得下载
                        loader: 'url-loader',
                        options: {
                            /*图片大小小于8kb，就会被base64处理  webpack的处理build文件目录下不会显示base64编码图片
                            优点: 减少请求数量（减轻服务器压力）
                            缺点：图片体积会更大（文件请求速度更慢）*/
                            limit: 8 * 1024,
                            /*
                              问题：因为url-loader默认使用es6模块化解析，而html-loader引入图片是commonjs
                              解析时会出问题：[object Module]
                              解决：关闭url-loader的es6模块化，使用commonjs解析*/
                            esModule: false,
                            // 给图片进行重命名 [hash:10] ==>取图片的hash的前10位  [ext]==>取文件原来扩展名
                            name: '[hash:10].[ext]',
                            /* 给打包后的图片重新命名  img：文件夹名字 [name]原文件名字 + 8位(原24位)hash值.扩展名
                             name:'img/[name].[hash:8].[ext]'*/
                            outputPath: "assets"
                        }
                    },
                    //处理html src里面的图片
                    {
                        test: /\.html$/,
                        // 处理html文件的img图片? ==> src的路径  （负责引入img，从而能被url-loader进行处理）
                        loader: 'html-loader'
                    },
                    //处理其他资源 比如阿里的字体图标 除了可以用test===include刷选 exclude也可以(优先级高)
                    // 打包其他资源(除了html/js/css资源以外的资源)
                    {
                        // 排除css|js|html|less|json|jpg|png|jepg资源
                        exclude: /\.(css|js|html|less|json|jpg|png|jepg)$/,
                        // include
                        use: [
                            {
                                loader: 'file-loader',
                                options: {
                                    name: '[hash:10].[ext]'
                                }
                            }
                        ]
                    },

                    /*
                      语法检查 规范代码： 下载安装eslint-loader , eslint
                        注意：只检查自己写的源代码，第三方的库是不用检查的
                        ★ package.json中eslintConfig中设置~设置检查规则：
                            "eslintConfig": {
                              "extends": "airbnb-base"
                            }
                            怎么检查？ 按...规则
                            js风格指南:airbnb --> 下载安装eslint-config-airbnb-base, eslint-plugin-import, eslint */


                    /*
                   正常来讲，一个文件只能被一个loader处理。
                   当一个文件要被多个loader处理，那么一定要指定loader执行的先后顺序：
                   必须先执行eslint 再执行babel  使用enforce:'pre'  */

                /*    {
                        test: /\.js$/,
                        //要排除
                        exclude: /node_modules/,
                        loader: 'eslint-loader',
                        enforce:'pre',
                        include:resolve(__dirname,"src") // 只检查src下面的js文件
                        options: {
                            // 自动修复 (你写的代码hui被eslint识别并改正)  会有警告 不会爆红
                            fix: true
                        }
                    },*/

                    /*  npm i --save-dev
                          js兼容性处理es6=>es5：@babel/core  核心库
                            1. 基本js兼容性处理 --> babel-loader @babel/preset-env
                              问题：目前只能转换基本语法，如promise高级语法不能转换

                            2. 全部js兼容性处理 --> @babel/polyfill（在js文件里面import引入即可）
                              问题：我只要解决部分兼容性问题，但是将所有兼容性代码全部引入，体积太大了~

                            3. 需要做兼容性处理的就做：按需加载  --> 下载core-js

                            一般是 第一步+第三步 完成最终配置
                        */
                    {
                        test: /\.js$/,
                        exclude: /(node_modules|bower_components)/,
                        use:[
                            //    开启多进程打包（了解即可）。进程启动大概为600ms，进程通信也有开销;只有工作消耗时间比较长，才需要多进程打包
                       /*     {
                                loader: 'thread-loader',
                                options: {
                                    workers: 2 // 进程2个
                                }
                            },*/
                            {
                                loader: 'babel-loader',
                                options: {
                                    // 预设：指示babel做怎么样的兼容性处理
                                    presets: [
                                        [
                                            '@babel/preset-env',
                                            {
                                                // 这就是按需加载
                                                useBuiltIns: 'usage',
                                                // 针对ES6的 promise 等语法 识别不了
                                                // 指定core-js版本 与下载版本一致
                                                corejs: {
                                                    version: 3
                                                },
                                                // 指定兼容性做到哪个版本浏览器
                                                targets: {
                                                    chrome: '60',
                                                    firefox: '60',
                                                    ie: '9',
                                                    safari: '10',
                                                    edge: '17'
                                                }

                                            }
                                        ]
                                    ],
                                    // 性能优化: 开启babel缓存
                                    // 第二次构建时，会读取之前的缓存
                                    cacheDirectory: true,
                                    cacheCompression:false, // 关闭缓存文件压缩
                                    // "plugins": ["@babel/plugin-transform-runtime"]  上述报错则安装以下这个  减少代码体积
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    // plugins的配置
    plugins: [
        // plugins的配置
        // html-webpack-plugin
        // 功能：默认会创建一个空的HTML，自动引入打包输出的所有资源（JS/CSS） 但是看不到css文件...
        // 需求：需要有结构的HTML文件
        new HtmlWebpackPlugin({
            // template: 复制 './src/index.html' 文件，并自动(不要自己引)引入打包输出的所有资源（JS/CSS）
            template: './src/index.html',
            filename: 'index.html',   // 输出的文件名
            // 压缩html代码
            minify: {
                // html里面移除空格
                // collapseWhitespace: true,
                //  html里面移除注释
                // removeComments: true
            }
        }),

        // 提取css （如何输出多个css文件?）
        new MiniCssExtractPlugin({
            // 对输出的css文件进行重命名  基于输出的path路径
            // filename: 'css/built[hash:10].css',
            filename: 'css/built[contenthash:10].css'
            // filename: 'css/[name].css'
        }),

        // 压缩css★
        new OptimizeCssAssetsWebpackPlugin(),
        // new UglifyJsPlugin()  // dev模式手动压缩js

        //使用插件
        new WorkboxWebpackPlugin.GenerateSW({
            /*
              1. 帮助serviceworker快速启动
              2. 删除旧的 serviceworker
                 生成一个 serviceworker 配置文件~
            */
            clientsClaim: true,
            skipWaiting: true
        }),

        // 告诉webpack哪些库不参与打包（因为已经被webpack --config webpack.dll.js  打包过了 ），同时使用时的名称也得变~
        new webpack.DllReferencePlugin({
            manifest: resolve(__dirname, 'dll/manifest.json')
        }),
        // 将webpack不参与打包 的库 自动引入该资源 到html中（webpack --config webpack.dll.js打包过后的文件）
        new AddAssetHtmlWebpackPlugin({
            filepath: resolve(__dirname, 'dll/jquery.js')
        })
    ],

    /* 代码分割js的方式2 可以和方式1连用:
       1. 可以将node_modules中代码单独打包一个chunk最终输出
       2. 自动分析多入口chunk中，有没有公共的文件（例如多个js文件都引入了jq）。如果有会把jq打包成单独一个chunk，而不是多次打包jq
   */
    optimization: {
        splitChunks: {
            chunks: 'all'
        },
        // 解决问题：修改a文件导致b文件的contenthash变化  (解决因某个js 的修改导致其他js的缓存失效)
        runtimeChunk: {
            name: (entrypoint) => `runtime-${entrypoint.name}`
        },
    },
    // 模式
    mode: 'development', // 开发模式
    // mode: 'production' // 生产模式会默认多个js 压缩  (无需再去使用UglifyJsPlugin)

    // 开发服务器 devServer：用来自动化（自动编译，自动打开浏览器，自动刷新浏览器~~）
    // 特点：只会在内存中编译打包，不会有任何输出
    // 启动devServer指令为：npx webpack-dev-server
    devServer: {
        // 项目构建后路径
        contentBase: resolve(__dirname, 'build'),
        // contentBase:"./build",
        inline:true,     //是否实时监听
        // 监视 contentBase 目录下的所有文件，一旦文件变化就会 reload
        watchContentBase: true,
        watchOptions: {
            // 忽略(不监视哪些)文件
            ignored: /node_modules/
        },
        compress: true,  // 启动gzip压缩
        // host:'192.168.0.100', // 项目启用地址 ==>手机也能访问
        port: 3001,      // 默认8080
        open: true, // 自动打开浏览器
        clientLogLevel: 'none',     // 不要显示启动服务器日志信息
        quiet: true,    // 除了一些基本启动信息以外，其他内容都不要显示
        overlay:true,//(浏览器错误遮罩)
        hot:true , //开启热部署
        /* historyApiFallback() {

        }*/
        // 服务器代理
        proxy: {
            // 一旦devServer(3001)服务器接受到 /api/xxx 的请求，就会把请求转发到另外一个服务器(3000)
            // Axios发送请求代码里面有/api的时候生效
            '/api': {
                target: 'http://localhost:3000',
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    },
    // devtool: 'source-map', 基本配置
    devtool: 'eval-source-map',


    // externals防止将某些 import 的包(package)打包到 bundle 中,这个有用
    // 而是在运行时(runtime)再去从外部获取这些扩展依赖
    externals: {
        // 拒绝jQuery被打包进来（针对CSDN引入）
        // jquery: 'jQuery'
    },

    // resolve：解析模块的规则
    resolve: {
        // 引入别名配置
        alias: {
            //用的时候用前面的
            $css: resolve(__dirname, 'src/css'),
            '@': resolve('src'),
        },
        // 配置省略文件路径的后缀名（知道为啥 有时候不需要写 xxx.jsx  xxx.vue 的后缀了吧）
        extensions: ['.js', '.json', '.jsx', '.css'],
        // 告诉 webpack 解析模块是去找哪个目录   这里是直接去外面找 node_modules
        modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
    }
}
/* 性能优化1
  HMR: hot module replacement 热模块替换 / 模块热替换
    作用：一个模块发生变化，只会重新打包这一个模块（而不是打包所有模块）
      极大提升构建速度

      css样式文件：可以使用HMR功能：因为style-loader内部实现了~   use ['style-loader','css-loader'] ★★★★★

      js文件：默认不能使用HMR功能 --> 需要修改js代码，添加支持HMR功能的代码
        注意：HMR功能对js的处理，只能处理非入口js文件的其他文件（但是HMR代码写在index.js里面

      html无需HMR 因为只有一个html文件  (html的hmr 不用做)
      html文件: 默认不能使用HMR功能.同时会导致问题：html文件不能热更新了
        解决：修改entry入口，将html文件引入
*/



/* 了解即可
  source-map: 一种 提供源代码到构建后代码映射 技术 （如果构建后代码出错了，通过映射可以追踪源代码错误）
    [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

    source-map：外部
    浏览器显示  错误代码准确信息 和 源代码的错误位置

    inline-source-map：内联
      只生成一个内联source-map
     浏览器显示 错误代码准确信息 和 源代码的错误位置

    hidden-source-map：外部 (隐藏源码)
     浏览器显示 错误代码错误原因，但是没有错误位置
     浏览器  不能追踪源代码错误，只能提示到构建后代码的错误位置

    eval-source-map：内联
      每一个文件都生成对应的source-map，都在eval函数中
     浏览器显示 错误代码准确信息 和 源代码的错误位置

    nosources-source-map：外部 (隐藏源码)
     浏览器有 错误代码准确信息, 但是没有任何源代码信息(定位不到源代码)

    cheap-source-map：外部
      错误代码准确信息 和 源代码的错误位置
     浏览器的 错误代码信息 只能精确到某行

    cheap-module-source-map：外部
      浏览器有 错误代码准确信息 和 源代码的错误位置
      module会将loader的source map加入

    内联 和 外部的区别：1. 外部生成相应文件，内联没有 2. 内联构建速度更快

    开发环境考虑：考虑 速度快，调试更友好
      速度快慢程度(eval>inline>cheap>...)
        eval-cheap-souce-map 最快
        eval-source-map  快
      调试友好程度
        souce-map
        cheap-module-souce-map 最友好
        cheap-souce-map
       折中点：使用eval-source-map||eval-cheap-module-souce-map

    生产环境考虑：源代码要不要隐藏? 调试要不要更友好

      生产环境一律不用内联 因为会使代码体积变大
      nosources-source-map 全部隐藏
      hidden-source-map 只隐藏源代码，会提示构建后代码错误信息
      折中：source-map / cheap-module-souce-map
*/


/*
  性能优化2:缓存问题
   1 babel缓存
      cacheDirectory: true  目的:让第二次打包构建速度更快 ★★★

   2 文件资源缓存
      hash: 每次webpack构建(运行一下)时会生成一个唯一的hash值。
      问题:  因为js和css同时使用一个hash值。
            如果重新打包，会导致  所有缓存失效。（虽然可能我只改动一个文件）

      chunkhash：根据chunk生成的hash值。如果打包来源于同一个chunk，那么hash值就一样
        问题: js和css的hash值还是一样的
              因为css是在js中被引入的，所以同属于一个chunk

     (所以用这个)★★★★★ contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
      --> 让代码上线运行缓存更好使用 ★★★
*/


/*性能优化3:  tree shaking ==>去除  无用代码

    前提：1. 必须使用ES6模块化  2. 开启production环境 此时自动开启tree shaking ★★★★★ (自动js丑化压缩)
    作用: 减少代码体积(例如有些函数虽然声明了但是没有使用  就会被树妖)

    在package.json中配置
      "sideEffects": false 所有代码都没有副作用（都进行tree shaking）
        问题：可能会把css / @babel/polyfill （直接引用没有使用的文件副作用）干掉

        任意的css less  不会进行tree shaking  结果就是  打包过后就会有  css...etc
        "sideEffects": ["*.css", "*.less"]
*/

/*
    代码分割1  配置多入口
    代码分割2  配置optimization
    戴码分割3  抛弃1,使用2并 在js文件配置 单独打包该js文件  形成方式3  一般用这个
 */

/*
optimization生产环境配置
splitChunks   runtimeChunk  minimizer  平行配置
optimization: {
    splitChunks: {
        chunks: 'all'
        // 默认值，可以不写~
        /!* minSize: 30 * 1024, // 分割的chunk最小为30kb
        maxSiza: 0, // 最大没有限制
        minChunks: 1, // 要提取的chunk最少被引用1次
        maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
        maxInitialRequests: 3, // 入口js文件最大并行请求数量
        automaticNameDelimiter: '~', // 名称连接符
        name: true, // 可以使用命名规则

        cacheGroups: {
          // 分割chunk的组
          // node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
          // 满足上面的公共规则，如：大小超过30kb，至少被引用一次。
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            // 优先级
            priority: -10
          },
          default: {
            // 要提取的chunk最少被引用2次
            minChunks: 2,
            // 优先级稍微低一点
            priority: -20,
            // 如果当前要打包的模块，和之前已经被提取的模块是同一个，就会复用，而不是重新打包模块 提速
            比如JQ打包成一个chunk之后复用不会被重复打包
            reuseExistingChunk: true
          }
        }*!/
    },
    // 将当前模块的记录其他模块的hash单独打包为一个文件 runtime  形成一对一关系
    // 解决问题：修改a文件导致b文件的contenthash变化  (解决因某个js 的修改导致其他js的缓存失效)
    runtimeChunk: {
        name: entrypoint => `runtime-${entrypoint.name}`
    },
    minimizer: [
        // 代替unify丑化   生产环境的压缩方案：js和css
        new TerserWebpackPlugin({
            // 开启缓存
            cache: true,
            // 开启多进程打包
            parallel: true,
            // 启动source-map
            sourceMap: true
        })
    ]
}*/
